package com.bigshen.spring.framework.context;

import com.bigshen.spring.framework.annonation.Autowired;
import com.bigshen.spring.framework.aop.JDKDynamicAOPProxy;
import com.bigshen.spring.framework.aop.config.AopConfig;
import com.bigshen.spring.framework.aop.support.AopAdvisedSupport;
import com.bigshen.spring.framework.beans.BeanWrapper;
import com.bigshen.spring.framework.beans.config.BeanDefinition;
import com.bigshen.spring.framework.beans.support.BeanDefinitionReader;

import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;

/**
 * Spring上下文
 * @Author eddy.shen
 * @Date 2020/4/18 11:26
 **/
public class ApplicationContext {

    private BeanDefinitionReader reader;
    List<BeanDefinition> beanDefinitionList = new ArrayList<>();
    Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    // 原生对象集合
    Map<String, Object> factoryBeanSourceMap = new HashMap<>();
    // 封装为beanWrapper的集合
    Map<String, BeanWrapper> factoryBeanInstanceCache = new HashMap<>();

    public ApplicationContext(String... configLocations) {

        try {
            // 创建BeanDefinition解析器，加载配置
            reader = new BeanDefinitionReader(configLocations);
            // 解析器解析封装为BeanDefinition
            beanDefinitionList = reader.doLoadBeanDefinition();
            // 放入内存
            doRegistBeanDefinition(beanDefinitionList);
            // 进行依赖注入
            doAutowired();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void doAutowired() {

        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            // 调用getBean进行初始化和依赖注入
            getBean(beanName);
        }

    }


    private Object getBean(String beanName) {

        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        // 反射创建对象
        Object instance = instantiateBean(beanName, beanDefinition);
        // 封装为beanWrapper
        BeanWrapper beanWrapper = new BeanWrapper(instance);
        // 放入IOC容器
        factoryBeanInstanceCache.put(beanName, beanWrapper);
        // 进行依赖注入
        populateBean(beanName, beanDefinition, beanWrapper);

        return beanWrapper.getWrapperInstance();
    }

    private void populateBean(String beanName, BeanDefinition beanDefinition, BeanWrapper beanWrapper) {

        Object instance = beanWrapper.getWrapperInstance();

        Class<?> clazz = beanWrapper.getWrapperClass();

        //把所有的包括private/protected/default/public 修饰字段都取出来
        for (Field field : clazz.getDeclaredFields()) {
            if(!field.isAnnotationPresent(Autowired.class)){ continue; }

            Autowired autowired = field.getAnnotation(Autowired.class);

            //如果用户没有自定义的beanName，就默认根据类型注入
            String autowiredBeanName = autowired.value().trim();
            if("".equals(autowiredBeanName)){
                //field.getType().getName() 获取字段的类型
                autowiredBeanName = field.getType().getName();
            }

            //暴力访问
            field.setAccessible(true);

            try {
                if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){
//                    // 属性依赖注入时，未找到对应值，将该对象放入循环引用集合，等待第一轮初始化完毕进行赋值
//                    if (!unDIBeanNameList.contains(beanName)) {
//                        unDIBeanNameList.add(beanName);
//                    }
//                    continue;
                    // 属性需要注入时，先将属性进行实例化注入
                    getBean(field.getType().getName());
                }
                //ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                continue;
            }
        }

    }

    /**
     * Spring实例化对象
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object instantiateBean(String beanName, BeanDefinition beanDefinition) {
        Object instance = null;
        if (factoryBeanSourceMap.containsKey(beanName)) {
            return factoryBeanSourceMap.get(beanName);
        }

        Class clazz = null;
        try {
            clazz = Class.forName(beanDefinition.getBeanClassName());
            instance = clazz.newInstance();

            // ------------对象实例化、依赖注入前进行 AOP --------------
            // 加载AOP配置
            AopAdvisedSupport support = initAopConfig();
            // 解析切面配置规则，绑定clazz内method与advice通知的关系
            support.setTargetClass(clazz);
            support.setTarget(instance);
            if (support.pointCutMatchClass()) {
                // 当前bean class存在配置的切面 生成代理类
                JDKDynamicAOPProxy aopProxy = new JDKDynamicAOPProxy(support);
                instance = aopProxy.getProxyInstance();
            }

            // ---------------- AOP 结束 ---------------------

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        this.factoryBeanSourceMap.put(beanName, instance);
        return instance;
    }

    private AopAdvisedSupport initAopConfig() {
        AopConfig aopConfig = new AopConfig();
        aopConfig.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        aopConfig.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        aopConfig.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        aopConfig.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        aopConfig.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        aopConfig.setAspectAfterThrowName(this.reader.getConfig().getProperty("aspectAfterThrowName"));
        return new AopAdvisedSupport(aopConfig);
    }

    private void doRegistBeanDefinition(List<BeanDefinition> beanDefinitionList) throws Exception {

        for (BeanDefinition beanDefinition : beanDefinitionList) {
            if(this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
                throw new Exception("The " + beanDefinition.getFactoryBeanName() + "is exists");
            }
            beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
            beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
        }

    }

}
